package com.nioSocketCore.chapter03;

import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.Selector;
import java.nio.channels.SelectionKey;
import java.nio.channels.SelectableChannel;
import java.net.ServerSocket;
import java.net.InetSocketAddress;
import java.util.Iterator;

/**
 * Simple echo-back server which listens for incoming stream connections and
 * echoes back whatever it reads. A single Selector object is used to listen to
 * the server socket (to accept new connections) and all the active socket
 * channels.
 * <p/>
 * 例 4-1. 使用 select( )来为多个通道提供服务
 *
 * @author Ron Hitchens (ron@ronsoft.com)
 */
public class SelectSockets {
    private static int PORT_NUMBER = 1234;


    private void go() throws Exception {


        System.out.println("INFO   Listening on port " + PORT_NUMBER);

        ServerSocketChannel serverChannel = ServerSocketChannel.open();// Allocate an unbound server socket channel

        ServerSocket serverSocket = serverChannel.socket(); // Get the associated ServerSocket to bind it with

        Selector selector = Selector.open();// Create a new Selector for use below

        serverSocket.bind(new InetSocketAddress(PORT_NUMBER));  // Set the port the server channel will listen to

        serverChannel.configureBlocking(false);  // Set nonblocking mode for the listening socket

        serverChannel.register(selector, SelectionKey.OP_ACCEPT);  // Register the ServerSocketChannel with the Selector
        while (true) {

            int n = selector.select();  // This may block for a long time. Upon returning, the selected set contains keys of the ready channels.
            if (n == 0) {
                continue; // nothing to do
            }

            Iterator it = selector.selectedKeys().iterator();  // Get an iterator over the set of selected keys

            while (it.hasNext()) {  // Look at each key in the selected set
                SelectionKey key = (SelectionKey) it.next();

                if (key.isAcceptable()) {  // Is a new connection coming in?
                    ServerSocketChannel server =(ServerSocketChannel) key.channel();
                    SocketChannel channel = server.accept();
                  //  registerChannel(selector, channel,SelectionKey.OP_READ);
                    channel.register(selector,  SelectionKey.OP_READ);

                    sayHello(channel);
                }
                if (key.isReadable()) {  // Is there data to read on this channel?
                    readDataFromSocket(key);
                }

                it.remove();  // Remove key from selected set; it's been handled
            }
        }
    }
// ----------------------------------------------------------



    private ByteBuffer buffer = ByteBuffer.allocateDirect(1024);  // Use the same byte buffer for all channels. A single thread is servicing all the channels, so no danger of concurrent acccess.


    /**
     * Sample data handler method for a channel with data ready to read.
     *
     * @param key A SelectionKey object associated with a channel determined by
     *            the selector to be ready for reading. If the channel returns
     *            142
     *            an EOF condition, it is closed here, which automatically
     *            invalidates the associated key. The selector will then
     *            de-register the channel on the next select call.
     */
    protected void readDataFromSocket(SelectionKey key) throws Exception {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        int count;
        buffer.clear(); // Empty buffer

        while ((count = socketChannel.read(buffer)) > 0) {// Loop while data is available; channel is nonblocking
            buffer.flip(); // Make buffer readable  Send the data; don't assume it goes all at once
            while (buffer.hasRemaining()) {
                socketChannel.write(buffer);
            }
// WARNING: the above loop is evil. Because
// it's writing back to the same nonblocking
// channel it read the data from, this code can
// potentially spin in a busy loop. In real life
// you'd do something more useful than this.
            buffer.clear(); // Empty buffer
        }
        if (count < 0) {

            socketChannel.close();  // Close channel on EOF, invalidates the key
        }
    }



    /**
     * Spew a greeting to the incoming client connection.
     *
     * @param channel The newly connected SocketChannel to say hello to.
     */
    private void sayHello(SocketChannel channel) throws Exception {
        buffer.clear();
        buffer.put("Hi there!\r\n".getBytes());
        buffer.flip();
        channel.write(buffer);
    }


    public static void main(String[] argv) throws Exception {
        new SelectSockets().go( );
    }


    /**
     * Register the given channel with the given selector for the given
     * operations of interest
     */
    private void registerChannel(Selector selector,SelectableChannel channel, int ops) throws Exception {
        if (channel == null) {
            return; // could happen
        }

        channel.configureBlocking(false); // Set the new channel nonblocking

        channel.register(selector, ops); // Register it with the selector
    }
}